package cn.echisan.springbootjwtdemo.filter;

import cn.echisan.springbootjwtdemo.utils.JwtAuthenticationConverter;
import cn.echisan.springbootjwtdemo.utils.JwtTokenUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 用OncePerRequestFilter自己实现一个可以更加加深理解，
 * 这里使用BasicAuthenticationFilter仅仅是因为BasicAuthenticationFilter的校验也是从header中取token
 * 比较相似，可以参考写法
 * Created by echisan on 2018/6/23
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {

    private final RequestHeaderRequestMatcher requiresAuthenticationRequestMatcher;
    private JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();

    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
        //拦截header中带Authorization的请求
        this.requiresAuthenticationRequestMatcher = new RequestHeaderRequestMatcher(HttpHeaders.AUTHORIZATION);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        final boolean debug = this.logger.isDebugEnabled();
        //header没带token的，直接放过，因为部分url匿名用户也可以访问
        //如果需要不支持匿名用户的请求没带token，这里放过也没问题，因为SecurityContext中没有认证信息，后面会被权限控制模块拦截
        if (!requiresAuthentication(request)) {
            chain.doFilter(request, response);
            return;
        }
        try {
            UsernamePasswordAuthenticationToken authResult = authenticationConverter.convert(request);
            //header没带token的，直接放过，因为部分url匿名用户也可以访问
            //如果需要不支持匿名用户的请求没带token，这里放过也没问题，因为SecurityContext中没有认证信息，后面会被权限控制模块拦截
            // 如果请求头中没有Authorization信息则直接放行了
            if (authResult == null) {
                chain.doFilter(request, response);
                return;
            }

            // 如果请求头中有token，则进行解析，并且设置认证信息
            String username = authResult.getName();

            if (debug) {
                this.logger
                        .debug("Jwt Authentication Authorization header found for user '"
                                + username + "'");
            }

            SecurityContextHolder.getContext().setAuthentication(authResult);

            onSuccessfulAuthentication(request, response, authResult);
        } catch (AccountExpiredException e) {

            onUnsuccessfulAuthentication(request, response, e);
            return;
        }
        chain.doFilter(request, response);
    }

    protected boolean requiresAuthentication(HttpServletRequest request) {
        return requiresAuthenticationRequestMatcher.matches(request);
    }

    @Override
    protected void onSuccessfulAuthentication(HttpServletRequest request,
                                              HttpServletResponse response, Authentication authResult) throws IOException {
    }

    @Override
    protected void onUnsuccessfulAuthentication(HttpServletRequest request,
                                                HttpServletResponse response, AuthenticationException failed) throws IOException {
        //返回json形式的错误信息
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.setStatus(HttpServletResponse.SC_FORBIDDEN);
        String reason = "统一处理，原因：" + failed.getMessage();
        response.getWriter().write(reason);
        response.getWriter().flush();
    }
}
